import { NodePath } from '@babel/traverse';
import babel, { types as t } from '@babel/core';
import { filter, find, findIndex, uniq } from "lodash-es";
import { scriptAstUtil } from "../util";

const lifeCycleMap: Record<string, string> = {
    mounted: 'onReady'
};

const unsupprotedUiApis = [
    'openSysMap',
    'getDeviceInfo',
    'pickPhoneNumber',
    'showAlert',
    'showConfirm',
    'showPrompt',
    'showDialog',
    'hideDialog',
    'hasSplashscreen',
    'closeSplashscreen',
    'setStatusBarStyle',
    'triggerPagePullUp',
    'triggerPagePullDown',
    'getDeviceInfo',
    'useTouchID',
    'canUseTouchID',
    'onStatusbarTap',
    'onBackButtonClick',
    'offBackButtonClick',
    'setPagePopGesture',
    'getQuery',
    'openBrowser',
    'getPushInfo',
    'onPushClick',
    'clearPushMessage',
    'share',
    'startMeiqia',
    'getMeiqiaClientId',
    'setMeiqiaClientId',
    'arrayBufferToBase64',
    'base64ToArrayBuffer'
];


export declare namespace UiScript {
    interface Config {
        [name: string]: any;
    }
}

export class UiScript {
    node: any;
    props: any;

    private _config!: UiScript.Config;
    private _pages: any;

    constructor(public source: string, public isApp?: Boolean | undefined) {
        this.initNode();
    }

    get config() {
        if (!this._config) {
            let configNode = find(this.props, (prop) => {
                return prop.key.name === 'config';
            });
            if (configNode) {
                let result = eval(`({${scriptAstUtil.transformFromNode(configNode)}})`);
                this._config = result.config;
            }
        }
        return this._config;
    }

    get pages() {
        if (!this._pages) {
            let config = this.config;
            let pages = config.pages;
            let tabs = [];
            if (config.tabBar && config.tabBar.list) {
                tabs = config.tabBar.list.map((item: any) => item.pagePath);
            }
            this._pages = uniq([...pages, ...tabs]);
        }
        return this._pages;
    }

    initNode() {
        this.node = scriptAstUtil.transform(this.source);
        this.props = scriptAstUtil.getExportDefaultProperties(this.node);
    }

    async generator() {
        this.transformProperties();
        this.transformUi();
        if (this.isApp) {
            this.updateConfigPages();
            this.addOnLaunchToApp();
        }
        return this.generateCode();
    }

    transformProperties() {
        let props = this.props;
        for (let i = props.length - 1; i >= 0; i--) {
            let type = props[i].type;
            let keyName = props[i].key.name;
            if (type === 'ObjectMethod' && keyName === 'data') {
                this._tranformData(props, props[i]);
            }
            if (type === 'ObjectProperty' && keyName === 'methods') {
                this._transformMethods(props, props[i], i);
            }
            if (type === 'ObjectMethod' && keyName === 'mounted') {
                this._transformLifeCycle(props[i], keyName);
            }
            if (['created', 'computed', 'watch'].indexOf(keyName) > -1) {
                props.splice(i, 1);
            }
        }
    }

    transformUi() {
        let visitor = {
            Identifier: (path: any) => {
                if (path.node.name === 'ui') {
                    path.node.name = 'wx';
                }
            },
            ExpressionStatement: (path: NodePath<t.ExpressionStatement>) => {
                this.vistorExpressionStatement(path);
            }
        };
        // babel.traverse(this.node, visitor);
        babel.traverse(this.node, visitor);
    }

    vistorExpressionStatement(path: NodePath<t.ExpressionStatement>) {
        let n = path.node;
        if (scriptAstUtil.isMatchMethods(n, unsupprotedUiApis)) {
            // @ts-ignore
            let id = n.expression['callee'].object.name;
            // @ts-ignore
            let prop = n.expression['callee'].property.name;
            if (id === 'ui') {
                id = 'wx';
            }
            if (t.isBlockStatement(path.parent)) {
                let body = path.parent.body;
                let index = findIndex(body, item => item === n);
                const comment: t.Comment = {
                    type: 'CommentBlock',
                    value: `* TouchUI Tip:小程序不支持此API \n * ${id}.${prop}()\n `
                };
                let prevNode = body[index - 1];
                let nextNode = body[index + 1];
                if (index === 0) {
                    path.parent.innerComments = path.parent.innerComments || [];
                    path.parent.innerComments.push(comment);
                }
                else if (prevNode && !scriptAstUtil.isMatchMethods(prevNode, unsupprotedUiApis)) {
                    prevNode.trailingComments = prevNode.trailingComments || [];
                    prevNode.trailingComments.push(comment);
                }
                else if (nextNode && !scriptAstUtil.isMatchMethods(nextNode, unsupprotedUiApis)) {
                    nextNode.leadingComments = nextNode.leadingComments || [];
                    nextNode.leadingComments.push(comment);
                }
                body.splice(index, 1);
            }
        }
    }

    updateConfigPages() {
        let pagesNode = scriptAstUtil.getNodeFromRootByPath(this.node, 'config.pages');
        pagesNode.value.elements = this.pages.map((page: any) => {
            return { type: 'StringLiteral', value: page };
        });
    }

    addOnLaunchToApp() {
        this.node.program.body.unshift({
            type: 'ImportDeclaration',
            specifiers: [{
                type: 'ImportDefaultSpecifier',
                local: {
                    type: 'Identifier',
                    name: 'system'
                }
            }],
            importKind: 'value',
            source: {
                type: 'StringLiteral',
                value: './static/utils/system'
            }
        });
        this.props.push({
            type: 'ObjectMethod',
            method: true,
            key: {
                type: 'Identifier',
                name: 'onLaunch'
            },
            kind: 'method',
            params: [],
            body: {
                type: 'BlockStatement',
                body: [{
                    type: 'ExpressionStatement',
                    expression: {
                        type: 'CallExpression',
                        callee: {
                            type: 'MemberExpression',
                            object: {
                                type: 'Identifier',
                                name: 'system'
                            },
                            property: {
                                type: 'Identifier',
                                name: 'attachInfo'
                            }
                        }
                    }
                }]
            }
        });
    }
    _tranformData(props: any[], data: any) {
        let dataBody = data.body.body;
        let dataProps = scriptAstUtil.getReturnObjectProperties(dataBody);
        let globalData = find(dataProps, (o) => {
            return o.key?.name === 'globalData';
        });
        if (globalData) {
            dataProps = filter(dataProps, (o) => {
                return o.key?.name !== 'globalData';
            });
        }
        delete data.kind;
        delete data.generator;
        delete data.async;
        delete data.expression;
        delete data.body;
        data.method = false;
        data.type = 'ObjectProperty';
        data.value = {
            type: 'ObjectExpression',
            properties: dataProps
        };
        if (globalData) {
            props.unshift(globalData);
        }
    }
    _transformMethods(props: any, methods: any, i: number) {
        methods.value.properties.forEach((method: any) => {
            if (!this.isApp) {
                props.push(method);
            }
        });
        props.splice(i, 1);
    }
    _transformLifeCycle(lifeCycle: any, name: string) {
        if (lifeCycleMap[name]) {
            lifeCycle.key.name = lifeCycleMap[name];
        }
    }
    generateCode() {
        return scriptAstUtil.transformFromAst(
            this.node,
            (code: string) => code.replace(/wx\.getApp\(\)/g, 'getApp()')
        );
    }
}
